package com.googlecode.tracey.ui.core;

import com.googlecode.funclate.Model;
import com.googlecode.funclate.json.Json;
import com.googlecode.totallylazy.Callable1;
import com.googlecode.totallylazy.Function1;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.utterlyidle.Request;
import com.googlecode.utterlyidle.RequestBuilder;
import com.googlecode.utterlyidle.Response;
import com.googlecode.utterlyidle.annotations.FormParam;
import com.googlecode.utterlyidle.annotations.GET;
import com.googlecode.utterlyidle.annotations.POST;
import com.googlecode.utterlyidle.annotations.Path;
import com.googlecode.utterlyidle.annotations.QueryParam;
import com.googlecode.utterlyidle.handlers.HttpClient;
import com.googlecode.utterlyidle.html.Form;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.googlecode.funclate.Model.immutable.model;
import static com.googlecode.totallylazy.Maps.map;
import static com.googlecode.totallylazy.Pair.pair;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Uri.uri;
import static com.googlecode.tracey.ui.core.FormBuilder.modify;
import static com.googlecode.tracey.ui.core.Svg.svg;
import static com.googlecode.utterlyidle.MediaType.TEXT_HTML;
import static com.googlecode.utterlyidle.RequestBuilder.get;
import static com.googlecode.utterlyidle.html.Html.html;

@Path("tracey")
public class TraceyCoreResource {
    private final MessageServers messageServers;
    private final HttpClient client;
    private final SequenceDiagramGenerator sequenceDiagramGenerator;
    private final Extensions extensions;

    public TraceyCoreResource(MessageServers messageServers, HttpClient client, SequenceDiagramGenerator sequenceDiagramGenerator, Extensions extensions) {
        this.messageServers = messageServers;
        this.client = client;
        this.sequenceDiagramGenerator = sequenceDiagramGenerator;
        this.extensions = extensions;
    }

    @Path("view")
    @GET
    public Model view() {
        return model().add("messageServers", map(messageServers.messageServers()));
    }

    @Path("trace")
    @POST
    public Model trace(@FormParam("originalSubmitUrl") String originalSubmitUrl, @FormParam("originalSubmitMethod") String originalSubmitMethod, Request request) throws Exception {
        Response response = client.handle(RequestBuilder.modify(request).uri(uri(originalSubmitUrl)).method(originalSubmitMethod).build());
        String json = response.entity().toString();
        return parseJson().either().call(json).map(errorModel(json), sequenceDiagramModel());
    }

    @Path("searchForm")
    @GET
    public Model searchForm(@QueryParam("messageServer") String messageServer) throws Exception {
        Response response = client.handle(get(messageServer).accepting(TEXT_HTML).build());
        final Sequence<Form> forms = html(response.entity().toString()).forms();
        return model().add("forms", forms.map(proxy(messageServer)).toList());

    }

    private Callable1<? super Map<String, Object>, ? extends Model> sequenceDiagramModel() {
        return new Callable1<Map<String, Object>, Model>() {
            @Override
            public Model call(Map<String, Object> map) throws Exception {
                List<Map<String,Object>> messages = (List<Map<String, Object>>) map.get("messages");
                return model()
                        .add("sequenceDiagramSvg", sequenceDiagramSvg(messages))
                        .add("extensions", extensions.extensions())
                        .add("messages", sequence(messages).map(splitBody()).toList());
            }
        };
    }

    private Callable1<Map<String, Object>, Map<String,Object>> splitBody() {
        return new Callable1<Map<String, Object>, Map<String, Object>>() {
            @Override
            public Map<String, Object> call(Map<String, Object> map) throws Exception {
                Map<String, Object> answer = new HashMap<String, Object>();
                answer.put("body",map.get("body"));
                final HashMap<String, Object> properties = new HashMap<String, Object>(map);
                properties.remove("body");
                answer.put("properties", properties);
                return answer;
            }
        };
    }

    private Callable1<? super Exception, Model> errorModel(final String response) {
        return new Callable1<Exception, Model>() {
            @Override
            public Model call(Exception e) throws Exception {
                return model()
                        .add(ModelRenderer.TEMPLATE_NAME,"parsingError")
                        .add("exception", e)
                        .add("errorResponse", response);
            }
        };
    }

    private Function1<String, Map<String, Object>> parseJson() {
        return new Function1<String, Map<String, Object>>() {
            public Map<String, Object> call(String json) throws Exception {
                return Json.parse(json);
            }
        };
    }

    private Callable1<? super Form, ? extends Form> proxy(final String messageServer) {
        return new Callable1<Form, Form>() {
            @Override
            public Form call(Form form) throws Exception {
                return proxyForm(messageServer, form);
            }
        };
    }


    private Form proxyForm(String messageServer, Form firstForm) throws Exception {
        final String action = firstForm.action();

        final String originalSubmitUrl = withoutTrailingSlash(messageServer) + "/" + withoutInitialSlash(action);
        return modify(firstForm)
                .action("trace")
                .method("POST")
                .hiddenParameter(pair("originalSubmitUrl", originalSubmitUrl))
                .hiddenParameter(pair("originalSubmitMethod", firstForm.method()))
                .build();
    }


    private Svg sequenceDiagramSvg(List<Map<String, Object>> messages) {
        return svg(sequenceDiagramGenerator.generateSequenceDiagram(sequence(messages).map(toTraceyMessage()).toList()));
    }

    private String withoutInitialSlash(String action) {
        return action.replaceAll("^/", "");
    }

    private String withoutTrailingSlash(String messageServer) {
        return messageServer.replaceAll("/$", "");
    }

    private Callable1<Map<String, Object>, TraceyMessage> toTraceyMessage() {
        return new Callable1<Map<String, Object>, TraceyMessage>() {
            @Override
            public TraceyMessage call(Map<String, Object> map) throws Exception {
                return new TraceyMessageFromMap(map);
            }
        };
    }
}
